package com.apex.shared.core.utils;

import jakarta.servlet.http.HttpServletRequest;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * IP地址工具类，提供IP地址的查询、验证、转换等功能
 *
 * @author MoNaiHui
 */
@Slf4j
@UtilityClass
public class IpUtils {

    /** 未知IP地址标识 */
    private static final String UNKNOWN = "unknown";
    /** 本地IP地址 */
    private static final String LOCALHOST = "127.0.0.1";
    /** 多IP地址分隔符 */
    private static final String SEPARATOR = ",";
    /** 代理服务器请求头：X-Forwarded-For */
    private static final String HEADER_X_FORWARDED_FOR = "x-forwarded-for";
    /** 代理服务器请求头：Proxy-Client-IP */
    private static final String HEADER_PROXY_CLIENT_IP = "Proxy-Client-IP";
    /** 代理服务器请求头：WL-Proxy-Client-IP */
    private static final String HEADER_WL_PROXY_CLIENT_IP = "WL-Proxy-Client-IP";

    /** IP地址查询器，使用volatile保证多线程可见性 */
    private static volatile Searcher searcher;
    /** 用于同步初始化searcher的锁对象 */
    private static final Object lock = new Object();

    /** IP地理位置信息缓存，使用ConcurrentHashMap保证线程安全 */
    private static final Map<String, CacheEntry> ipRegionCache = new ConcurrentHashMap<>(256);
    /** 缓存过期时间：30分钟 */
    private static final long CACHE_EXPIRE_TIME = TimeUnit.MINUTES.toMillis(30);

    /**
     * 缓存条目内部类，包含值和过期时间
     */
    private static class CacheEntry {
        /** 缓存的地理位置信息 */
        final String value;
        /** 过期时间戳 */
        final long expireTime;

        CacheEntry(String value) {
            this.value = value;
            this.expireTime = System.currentTimeMillis() + CACHE_EXPIRE_TIME;
        }

        /**
         * 判断缓存是否已过期
         */
        boolean isExpired() {
            return System.currentTimeMillis() > expireTime;
        }
    }

    /**
     * 获取IP地址查询器实例
     * 使用双重检查锁定模式实现延迟加载
     */
    private static Searcher getSearcher() {
        if (searcher == null) {
            synchronized (lock) {
                if (searcher == null) {
                    try {
                        // 从classpath加载IP地址库文件
                        ClassPathResource resource = new ClassPathResource("ip2region.xdb");
                        byte[] bytes = resource.getContentAsByteArray();
                        searcher = Searcher.newWithBuffer(bytes);
                    } catch (IOException e) {
                        log.error("IP2Region 初始化失败", e);
                    }
                }
            }
        }
        return searcher;
    }

    /**
     * 获取本机IP地址
     * 优先获取外网IPv4地址，如果没有则获取内网IPv4地址
     * 如果都获取失败则返回127.0.0.1
     */
    public static String getHostIp() {
        try {
            // 优先获取外网IP
            String ip = getOuterNetworkIp();
            if (ip != null) {
                return ip;
            }
            // 获取内网IP
            return getInnerNetworkIp();
        } catch (Exception e) {
            return LOCALHOST;
        }
    }

    /**
     * 获取外网IPv4地址
     * 通过遍历网络接口获取非回环、非内网的IPv4地址
     */
    private static String getOuterNetworkIp() {
        try {
            Enumeration<NetworkInterface> networks = NetworkInterface.getNetworkInterfaces();
            while (networks.hasMoreElements()) {
                NetworkInterface network = networks.nextElement();
                // 排除回环和关闭的网络接口
                if (network.isLoopback() || !network.isUp()) {
                    continue;
                }

                Enumeration<InetAddress> addresses = network.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    InetAddress address = addresses.nextElement();
                    // 筛选条件：非回环、非内网、非任意地址、IPv4
                    if (!address.isLoopbackAddress()
                            && !address.isSiteLocalAddress()
                            && !address.isAnyLocalAddress()
                            && address.getHostAddress().indexOf(':') == -1) {
                        return address.getHostAddress();
                    }
                }
            }
        } catch (SocketException e) {
            log.error("获取外网IP失败", e);
        }
        return null;
    }

    /**
     * 获取内网IPv4地址
     * 优先获取本地主机地址，如果是IPv6则遍历网络接口获取内网IPv4地址
     */
    private static String getInnerNetworkIp() {
        try {
            InetAddress localHost = InetAddress.getLocalHost();
            // 如果是IPv4则直接返回
            if (localHost.getHostAddress().indexOf(':') == -1) {
                return localHost.getHostAddress();
            }

            // 如果是IPv6，则遍历网络接口寻找内网IPv4
            Enumeration<NetworkInterface> networks = NetworkInterface.getNetworkInterfaces();
            while (networks.hasMoreElements()) {
                NetworkInterface network = networks.nextElement();
                if (network.isLoopback() || !network.isUp()) {
                    continue;
                }

                Enumeration<InetAddress> addresses = network.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    InetAddress address = addresses.nextElement();
                    // 筛选条件：非回环、内网地址、IPv4
                    if (!address.isLoopbackAddress()
                            && address.isSiteLocalAddress()
                            && address.getHostAddress().indexOf(':') == -1) {
                        return address.getHostAddress();
                    }
                }
            }
            return LOCALHOST;
        } catch (Exception e) {
            log.error("获取内网IP失败", e);
            return LOCALHOST;
        }
    }

    /**
     * 获取客户端真实IP地址
     * 支持多级代理，获取规则如下：
     * 1. 优先从x-forwarded-for获取第一个非unknown的IP
     * 2. 如果没有则尝试从Proxy-Client-IP获取
     * 3. 如果没有则尝试从WL-Proxy-Client-IP获取
     * 4. 如果都没有则获取RemoteAddr
     * 5. 如果是本地访问则返回本机IP
     */
    public static String getClientIp(HttpServletRequest request) {
        if (request == null) {
            return UNKNOWN;
        }

        String ip = request.getHeader(HEADER_X_FORWARDED_FOR);
        if (isUnknown(ip)) {
            ip = request.getHeader(HEADER_PROXY_CLIENT_IP);
        }
        if (isUnknown(ip)) {
            ip = request.getHeader(HEADER_WL_PROXY_CLIENT_IP);
        }
        if (isUnknown(ip)) {
            ip = request.getRemoteAddr();
            if (LOCALHOST.equals(ip)) {
                ip = getHostIp();
            }
        }

        // 多个代理的情况，第一个IP为客户端真实IP
        if (ip != null && ip.indexOf(SEPARATOR) > 0) {
            ip = ip.substring(0, ip.indexOf(SEPARATOR));
        }

        return "0:0:0:0:0:0:0:1".equals(ip) ? LOCALHOST : ip;
    }

    /**
     * 检查IP地址是否未知
     * 当IP地址为空或者为unknown时返回true
     */
    public static boolean isUnknown(String ip) {
        return !StringUtils.hasText(ip) || UNKNOWN.equalsIgnoreCase(ip);
    }

    /**
     * 判断是否为内网IP
     * 包括以下情况：
     * 1. 10.0.0.0/8
     * 2. 172.16.0.0/12
     * 3. 192.168.0.0/16
     * 4. 127.0.0.1
     */
    public static boolean isInnerIp(String ip) {
        if (!StringUtils.hasText(ip)) {
            return false;
        }

        try {
            InetAddress addr = InetAddress.getByName(ip);
            return addr.isSiteLocalAddress() || addr.isLoopbackAddress();
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 获取IP地址的地理位置
     * 使用ip2region库查询，支持缓存机制
     * 
     * @param ip IP地址
     * @return 地理位置信息，格式：国家|区域|省份|城市|ISP
     *         查询失败或内网IP返回unknown
     */
    public static String getIpRegion(String ip) {
        if (getSearcher() == null || !StringUtils.hasText(ip) || isInnerIp(ip)) {
            return UNKNOWN;
        }

        // 检查缓存
        CacheEntry entry = ipRegionCache.get(ip);
        if (entry != null && !entry.isExpired()) {
            return entry.value;
        }

        // 缓存未命中或已过期，重新查询
        try {
            String region = getSearcher().search(ip);
            ipRegionCache.put(ip, new CacheEntry(region));
            return region;
        } catch (Exception e) {
            log.error("获取IP地址[{}]的地理位置失败", ip, e);
            return UNKNOWN;
        }
    }

    /**
     * 检查IP地址格式是否正确
     * 规则：
     * 1. 必须是四段数字
     * 2. 每段数字范围为0-255
     * 3. 不能为空
     */
    public static boolean isValidIp(String ip) {
        if (!StringUtils.hasText(ip)) {
            return false;
        }
        try {
            String[] parts = ip.split("\\.");
            if (parts.length != 4) {
                return false;
            }
            for (String part : parts) {
                int value = Integer.parseInt(part);
                if (value < 0 || value > 255) {
                    return false;
                }
            }
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    /**
     * 检查是否为恶意IP
     * 可以通过接入威胁情报系统或自定义黑名单来实现
     * 
     * @param ip 要检查的IP地址
     * @return true表示是恶意IP，false表示不是恶意IP
     */
    public static boolean isMaliciousIp(String ip) {
        // TODO: 可以接入威胁情报系统或黑名单系统
        return false;
    }
} 